package com.dounine.clouddisk360.parser;

import com.dounine.clouddisk360.annotation.DependResult;
import com.dounine.clouddisk360.annotation.Dependency;
import com.dounine.clouddisk360.exception.CloudDiskException;
import com.dounine.clouddisk360.parser.deserializer.*;
import com.dounine.clouddisk360.parser.deserializer.authtoken.AuthTokenConst;
import com.dounine.clouddisk360.parser.deserializer.login.Login;
import com.dounine.clouddisk360.parser.deserializer.login.LoginUserToken;
import com.dounine.clouddisk360.pool.PoolingHttpClientConnection;
import com.dounine.clouddisk360.store.BasePathCommon;
import com.dounine.clouddisk360.store.CookieStoreUT;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpRequest;
import org.apache.http.client.CookieStore;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.HttpClients;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public abstract class BaseParser<Method extends HttpRequest, M extends BaseDes, C extends BaseConst, Parameter extends BaseParameter, RequestInterceptor extends BaseRequestInterceptor<C>, ResponseHandle, Parser extends BaseParser>
		extends JSONBianary {

	private static final Logger LOGGER = LoggerFactory.getLogger(BaseParser.class);

	static{
		if(StringUtils.isBlank(BasePathCommon.BASE_PATH)){
			BasePathCommon.BASE_PATH = FileUtils.getUserDirectoryPath()+"/";
		}
		LOGGER.info("360云盘模块初始化位置:"+ BasePathCommon.BASE_PATH);
	}

	public static final RequestConfig COOKIE_REQUEST_CONFIG = RequestConfig.custom()
			.setCookieSpec(CookieSpecs.STANDARD_STRICT).build();

	protected CookieStoreUT cookieStoreUT = new CookieStoreUT();
	protected HttpClientContext httpClientContext = new HttpClientContext();
	protected HttpClient httpClient = null;

	public RequestInterceptor requestInterceptor;
	public C CONST;
	public HttpRequest requestMethod;
	public ResponseHandler<M> responseHandler;
	public Parameter parameter;
	protected String uriPath;
	protected Class<M> mClazz;
	protected Map<String, M> dependencys = new HashMap<>(0);
	private CloudDiskException cloudDiskException = null;
	protected LocalDateTime createDateTime = LocalDateTime.now();

	public BaseParser dependsCustomInit(Parser parser, BaseParser baseParser) {
		return baseParser;
	}

	public <TT> TT getDependResult(Class<TT> depend) {
		com.dounine.clouddisk360.parser.deserializer.BaseDes baseDes = this.dependencys.get(depend.getName());
		if (null != baseDes) {
			return (TT) baseDes;
		}
		return null;
	}

	@SuppressWarnings("unchecked")
	private void inits() {
		Type genType = getClass().getGenericSuperclass();
		Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
		mClazz = (Class<M>) params[1];//首先初始化M返回结果类,execClouddiskException()方法要用到
		Dependency dependency = this.getClass().getAnnotation(Dependency.class);
		if (null != dependency) {
			final Class<BaseParser>[] dClass = (Class<BaseParser>[]) dependency.depends();
			for (Class<BaseParser> cc : dClass) {
				try {
					final DependResult dependResult = cc.getAnnotation(DependResult.class);
					BaseParser baseParser = cc.getConstructor(LoginUserToken.class).newInstance(loginUserToken);
					if(baseParser.hasException()){
						this.cloudDiskException = baseParser.getCloudDiskException();
					}else{
 						if (dependResult.customInit()) {
							baseParser = baseParser.dependsCustomInit(baseParser, this);
						} else {// 默认初始化
							final M baseResult = (M) baseParser.parse();
							if(baseParser.hasException()){
								this.cloudDiskException = baseParser.getCloudDiskException();
							}else if(null!=baseResult){
								// this.dependencys.putAll(baseParser.getDependencys());
								// //此句会把所有父级响应的结果往下传递
								dependencys.put(baseResult.getClass().getName(), baseResult);
                            }
                        }
					}
					this.dataSmooth(baseParser);
				}catch (CloudDiskException e) {
					this.cloudDiskException = e;
				}catch (InstantiationException | IllegalAccessException e) {
					this.cloudDiskException = new CloudDiskException(e);
				} catch (NoSuchMethodException e) {
					this.cloudDiskException = new CloudDiskException(e);
				} catch (SecurityException e) {
					this.cloudDiskException = new CloudDiskException(e);
				} catch (IllegalArgumentException e) {
					this.cloudDiskException = new CloudDiskException(e);
				} catch (InvocationTargetException e) {
					this.cloudDiskException = new CloudDiskException(e.getMessage());
				} finally {
					if(this.hasException()){//有异常程序返回
						return;
					}
				}
			}
		}

		Class<RequestInterceptor> requestInterceptorClazz = (Class<RequestInterceptor>) params[4];
		Class<ResponseHandler<M>> responseHandlerClazz = (Class<ResponseHandler<M>>) params[5];
		Class<HttpRequest> requestMethodClazz = (Class<HttpRequest>) params[0];
		Class<BaseConst> baseconstMethodClazz = (Class<BaseConst>) params[2];
		try {
			this.responseHandler = (ResponseHandler<M>) responseHandlerClazz.getConstructors()[0].newInstance(this);
			this.requestInterceptor = (RequestInterceptor) requestInterceptorClazz.getConstructors()[0].newInstance();
			this.requestMethod = (HttpRequest) requestMethodClazz.newInstance();
			CONST = (C) baseconstMethodClazz.newInstance();
		} catch (InstantiationException | IllegalAccessException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
	}

	public BaseParser() {
		inits();
	}

	public BaseParser(final LoginUserToken loginUser) {
		if (null == loginUserToken) {
			if (null == loginUser) {
				throw new CloudDiskException("loginUserToken 不能为空");
			}
			if (StringUtils.isBlank(loginUser.getAccount()) || StringUtils.isBlank(loginUser.getPassword())) {
				throw new CloudDiskException("loginUserToken 属性(用户名/密码)不能为空");
			}
		}
		cookieStoreUT.setLoginUserToken(loginUser);
		httpClientContext.setUserToken(loginUser);
		this.loginUserToken = loginUser;
		inits();
	}

	/**
	 * 把parse数据平滑过来
	 */
	public <Parser extends BaseParser> void dataSmooth(Parser parse) {
		if (null != parse) {
			if (null == parse.loginUserToken) {
				throw new CloudDiskException("loginUserToken不能为空");
			} else {
				if (StringUtils.isBlank(parse.loginUserToken.getAccount())) {
					throw new CloudDiskException("loginUserToken account不能为空");
				}
			}
			this.httpClientContext = parse.httpClientContext;
			super.setLoginUserToken(parse.loginUserToken);// jsonbianary
			cookieStoreUT.setLoginUserToken(parse.loginUserToken);
			httpClientContext.setUserToken(parse.loginUserToken);
			this.loginUserToken = parse.loginUserToken;
		} else {
			throw new CloudDiskException("{parse} 参数不能为空");
		}
	}

	public M parse() {
		if(hasException()){
			return execClouddiskException();
		}
		if (null == loginUserToken) {
			throw new CloudDiskException("至少给一个解析器初始化用户信息");
		}
		Method request = initRequest(parameter);
		M m = execute(request);
		cookieStoreUT.setCookieStore(httpClientContext.getCookieStore());
		return m;
	}

	public M parse(Parameter parameter) {
		if(hasException()){
			return execClouddiskException();
		}
		this.parameter = parameter;
		return parse();
	}

	public String getUriPath() {
		return null;
	}

	@SuppressWarnings("unchecked")
	public Method initRequest(Parameter parameter) {
		uriPath = getUriPath();
		if (StringUtils.isNotBlank(uriPath)) {
			if (requestMethod instanceof HttpGet) {
				return (Method) new HttpGet(getRequestHost() + uriPath);
			}
			return (Method) new HttpPost(getRequestHost() + uriPath);
		}
		throw new CloudDiskException("使用默认initRequest,uriPath不能为空");
	}

	protected RequestConfig cookieRequestConfig(String cookieSpecs) {
		return RequestConfig.custom().setCookieSpec(cookieSpecs).build();
	}

	public M execute(Method request) {
		if(hasException()){
			return execClouddiskException();
		}
		httpClient = HttpClients.custom().setConnectionManager(PoolingHttpClientConnection.getInstalce())
				.addInterceptorLast(requestInterceptor).setDefaultRequestConfig(COOKIE_REQUEST_CONFIG).build();
		try {
			if (request instanceof HttpGet) {
				HttpGet get = (HttpGet) request;
				return httpClient.execute(get, responseHandler, this.httpClientContext);
			}
			HttpPost post = (HttpPost) request;
			return httpClient.execute(post, responseHandler, this.httpClientContext);
		} catch (IOException e) {
            e.printStackTrace();
		}
        return null;
	}

	public String readCookieStoreValue(String name) {
		if (StringUtils.isBlank(name)) {
			throw new CloudDiskException("cookie名称不能为空");
		}
		CookieStore cookieStore = httpClientContext.getCookieStore();
		List<Cookie> cookies = cookieStore.getCookies();
		Cookie cookie = null;
		for (Cookie coo : cookies) {
			if (coo.getName().equals(name)) {
				cookie = coo;
				break;
			}
		}
		if (null != cookie) {
			return cookie.getValue();
		}
		return null;
	}

	public CookieStore readCookieStoreDisk() {
		httpClientContext.setCookieStore(cookieStoreUT.readCookieStoreForDisk());
		return httpClientContext.getCookieStore();
	}

	public CookieStore readCookieStoreDisk(String[] filterCookie) {
		httpClientContext.setCookieStore(cookieStoreUT.readCookieStoreForDisk(filterCookie));
		return httpClientContext.getCookieStore();
	}

	public HttpClientContext getHttpClientContext() {
		return httpClientContext;
	}

	public void setHttpClientContext(HttpClientContext httpContext) {
		this.httpClientContext = httpContext;
	}

	public CookieStoreUT getCookieStoreUT() {
		return cookieStoreUT;
	}

	public Parameter getParameter() {
		return parameter;
	}

	public void setParameter(Parameter parameter) {
		this.parameter = parameter;
	}

	public String getRequestHostName() {
		if (null == httpClientContext.getAttribute(BaseConst.REQUEST_HOST_NAME)) {
			throw new CloudDiskException(MsgConst.HOST_VALUE_NOT_NULL);
		} else {
			return String.valueOf(httpClientContext.getAttribute(BaseConst.REQUEST_HOST_NAME));
		}
	}

	public String getRequestHost() {
		if (null == httpClientContext.getAttribute(BaseConst.REQUEST_HOST_NAME)) {
			throw new CloudDiskException(MsgConst.HOST_VALUE_NOT_NULL);
		} else {
			return String.valueOf("http://" + httpClientContext.getAttribute(BaseConst.REQUEST_HOST_NAME));
		}
	}

	@SuppressWarnings("unchecked")
	public M getParseResult() {
		return (M) httpClientContext.getAttribute(mClazz.getName());
	}

	public Login getLoginInfo() {
		Object object = httpClientContext.getAttribute(AuthTokenConst.LOGIN_USER_INFO);
		if (null == object) {
			throw new CloudDiskException("读取登录用户信息失败");
		}
		Login login = (Login) object;
		return login;
	}
	
	public String readAuthToken(){
		Object token = httpClientContext.getAttribute("token");
		if(null==token){
			return "";
		}
		return String.valueOf(token);
	}

	public Map<String, M> getDependencys() {
		return dependencys;
	}

	public void setDependencys(Map<String, M> dependencys) {
		this.dependencys = dependencys;
	}

	public HttpClient getHttpClient() {
		return httpClient;
	}

	public void setHttpClient(HttpClient httpClient) {
		this.httpClient = httpClient;
	}

	public CloudDiskException getCloudDiskException() {
		return cloudDiskException;
	}

	public void setCloudDiskException(CloudDiskException cloudDiskException) {
		this.cloudDiskException = cloudDiskException;
	}

	public LocalDateTime getCreateDateTime() {
		return createDateTime;
	}

	public void setCreateDateTime(LocalDateTime createDateTime) {
		this.createDateTime = createDateTime;
	}

	public M execClouddiskException(){
		try {
			M m = mClazz.newInstance();
			m.setCddmsg(cloudDiskException.getMessage());
			return m;
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		return null;
	}

	public boolean hasException(){
		return null!=this.cloudDiskException;
	}
}
